// Class include
#include "CSerialPort.hpp"

// System includes
#include <stdio.h>    // Standard input/output definitions 
#include <unistd.h>   // UNIX standard function definitions 
#include <fcntl.h>    // File control definitions 
#include <errno.h>    // Error number definitions 
#include <termios.h>  // POSIX terminal control definitions 
#include <string.h>   // String function definitions 
#include <sys/ioctl.h>

// Other includes
#include "CTrace.hpp"

CSerialPort::CSerialPort(const char* device)
: mpDeviceName(device)
, mBaudRate(9600)
, mSerialHndl(0)
, mLock()
{
   do
   {
      int fd;
      fd = open(device, O_RDWR | O_NONBLOCK);

      if (fd == -1)
      {
         TRC_ERROR("Serialport_init: Unable to open port. Device=%s, Baud rate=%d", device, mBaudRate);
         break;
      }

      struct termios toptions;
      if (tcgetattr(fd, &toptions) < 0)
      {
        TRC_ERROR("Serialport_init: Couldn't get term attributes");
        break;
      }

      speed_t brate = mBaudRate; // let you override switch below if needed
      switch(mBaudRate)
      {
         case 4800:   brate=B4800;   break;
         case 9600:   brate=B9600;   break;
         #ifdef B14400
         case 14400:  brate=B14400;  break;
         #endif
         case 19200:  brate=B19200;  break;
         #ifdef B28800
         case 28800:  brate=B28800;  break;
         #endif
         case 38400:  brate=B38400;  break;
         case 57600:  brate=B57600;  break;
         case 115200: brate=B115200; break;
      }
      cfsetispeed(&toptions, brate);
      cfsetospeed(&toptions, brate);

      toptions.c_cflag &= ~PARENB;  // 8N1
      toptions.c_cflag &= ~CSTOPB;
      toptions.c_cflag &= ~CSIZE;
      toptions.c_cflag |= CS8;
      toptions.c_cflag &= ~CRTSCTS; // no flow control
      //toptions.c_cflag &= ~HUPCL; // disable hang-up-on-close to avoid reset
      toptions.c_cflag |= CREAD | CLOCAL;                   // turn on READ & ignore ctrl lines
      toptions.c_iflag &= ~(IXON | IXOFF | IXANY);          // turn off s/w flow ctrl
      toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  // make raw
      toptions.c_oflag &= ~OPOST;   // make raw
      toptions.c_cc[VMIN]  = 0;     // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
      toptions.c_cc[VTIME] = 0;
      //toptions.c_cc[VTIME] = 20;

      tcsetattr(fd, TCSANOW, &toptions);
      if (tcsetattr(fd, TCSAFLUSH, &toptions) < 0)
      {
        TRC_ERROR("Serialport_init: Couldn't set term attributes");
        break;
      }

      mSerialHndl = fd;
   } while(false);
}

CSerialPort::~CSerialPort()
{
   if (0 != mSerialHndl)
   {
      close(mSerialHndl);
   }
}

bool CSerialPort::sendByte(unsigned char b)
{
   int n = write(mSerialHndl, &b, 1);
   return (1 == n);
}

size_t CSerialPort::send(const unsigned char* str, size_t len)
{
   size_t retVal = 0;

   pthread_mutex_lock(&mLock);
   {
      if(false == isOpened())
      {
         return 0;
      }

      retVal = write(mSerialHndl, str, len);
      if (retVal != len)
      {
         TRC_WARNING("Serialport_write: couldn't write whole string. Wrote %d bytes from %d", retVal, len);
      }
   }
   pthread_mutex_unlock(&mLock);

   return retVal;
}

// http://stackoverflow.com/questions/2917881/how-to-implement-a-timeout-in-read-function-call
size_t CSerialPort::recv(unsigned char* buffer, size_t length)
{
   size_t retVal = 0;
   //int timeout = 200;
   
   //pthread_mutex_lock(&mLock);
   do
   {
      if(false == isOpened())
      {
         TRC_WARNING("CSerialPort::recv: Port is not opened");
         break;
      }

      if(0 == buffer)
      {
         TRC_WARNING("CSerialPort::recv: Buffer is empty");
         break;
      }

      char miniBuffer[1];  // read expects an array, so we give it a 1-byte array
      int i=0;

      do
      { 
         int numRead = read(mSerialHndl, miniBuffer, 1);  // read a char at a time
         if (numRead == -1)
         {
            retVal = numRead;
            TRC_ERROR("serialport_read: couldn't read");
            break;
         }

         if (numRead == 0)
         {
            usleep( 10 * 1000 );  // wait 10 msec try again
            //timeout--;
            continue;
         }

         buffer[i] = miniBuffer[0]; 
         i++;
         retVal = i;
      } while(i < length/* && timeout > 0*/);

      buffer[i] = 0;  // null terminate the string
   } while(false);
   //pthread_mutex_unlock(&mLock);

   return retVal;
}

bool CSerialPort::isOpened()
{
   return 0 != mSerialHndl;
}